package io.github.ali.commons.beanutils;

import java.lang.reflect.Array;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.BiFunction;

import net.sf.cglib.beans.BeanCopier;
import net.sf.cglib.core.Converter;
import org.apache.commons.collections.map.LRUMap;

/**
 * 对象属性复制工具类
 * 
 * @author bailey_fu
 * @data 2014-2-17
 * @version $Id: BeanCopierUtils.java, v 0.1 2014-2-17 下午04:46:18 bailey_fu Exp
 *          $
 * @Description
 */
public class BeanCopierUtils {
	private static Map<String, Object[]> CACHE = new LRUMap(100);
	private static Map<String, Field[][]> FIELD_CACHE = new LRUMap(100);
	private BeanCopierUtils() {
	}

	public static void setCacheSize(int maxSize) {
		if (maxSize > 0) {
			CACHE = new LRUMap(maxSize);
			FIELD_CACHE = new LRUMap(maxSize);
		}
	}
	
	/**
	 * copy properties
	 * 
	 * @param source
	 * @param target
	 * @param propertyConverter 转换器；会缓存，相同的source和target会使用同一个转换器
	 */
	public static void copyProperties(Object source, Object target, PropertyConverter propertyConverter) {
		copyProperties(source, target, propertyConverter, false);
	}
	/**
	 * copy properties
	 * @param source
	 * @param target
	 * @param propertyConverter
	 * @param replaceConverter 将缓存的转换器替换为当前参数的propertyConverter
	 */
	public static void copyProperties(Object source, Object target, PropertyConverter propertyConverter, boolean replaceConverter) {
		if (source == null || target == null) {
			return;
		}
		boolean useConverter = propertyConverter == null ? false : true;
		String beanKey = generateKey(source.getClass(), target.getClass(), useConverter);
		Object[] cachedCopier = CACHE.get(beanKey);
		if (cachedCopier == null) {
			cachedCopier = new Object[2];
			cachedCopier[0] = BeanCopier.create(source.getClass(), target.getClass(), useConverter);
			CACHE.put(beanKey, cachedCopier);
		}
		if (cachedCopier[1] == null || replaceConverter) {
			cachedCopier[1] = useConverter ? new Converter() {
				@Override
				public Object convert(Object value, @SuppressWarnings("rawtypes") Class target, Object context) {
					return propertyConverter.convert(value, target, context);
				}
			} : null;
		}
		((BeanCopier) cachedCopier[0]).copy(source, target, (Converter) cachedCopier[1]);
	}

	/**
	 * 根据两个class名组装成key
	 * 
	 * @param class1
	 * @param class2
	 * @return
	 */
	private static String generateKey(Class<?> class1, Class<?> class2, boolean useConverter) {
		return new StringBuilder().append(class1).append(class2).append(useConverter).toString();
	}

	/**
	 * 拷贝对象
	 * 
	 * @param source
	 * @param target
	 * @return
	 * @throws RuntimeException
	 */
	public static <T> T copyOne2One(Object source, Class<T> target) throws RuntimeException {
		return copyOne2One(source, target, null, false);
	}
	public static <T> T copyOne2One(Object source, Class<T> target,PropertyConverter propertyConverter) throws RuntimeException {
		return copyOne2One(source, target, propertyConverter, false);
	}
	public static <T> T copyOne2One(Object source, Class<T> target,PropertyConverter propertyConverter , boolean replaceConverter) throws RuntimeException {
		if (source == null || target == null) {
			return null;
		}
		T instance = null;
		try {
			instance = target.newInstance();
			copyProperties(source, instance, propertyConverter, replaceConverter);
		} catch (Exception e) {
			throw new RuntimeException(e);
		}
		return instance;
	}

	/**
	 * 拷贝集合
	 * 
	 * @param source
	 * @param target
	 * @return
	 * @throws RuntimeException
	 */
	public static <T> List<T> copyList2List(List<?> source, Class<T> target) throws RuntimeException {
		return copyList2List(source, target, null, false);
	}
	public static <T> List<T> copyList2List(List<?> source, Class<T> target,PropertyConverter propertyConverter) throws RuntimeException {
		return copyList2List(source, target, propertyConverter, false);
	}
	public static <T> List<T> copyList2List(List<?> source, Class<T> target,PropertyConverter propertyConverter, boolean replaceConverter) throws RuntimeException {
		if (source == null || target == null) {
			return null;
		}
		if (source.isEmpty()) {
			return new ArrayList<T>();
		}
		List<T> result = new ArrayList<T>();
		for (Object obj : source) {
			result.add(copyOne2One(obj, target, propertyConverter, replaceConverter));
		}
		return result;
	}
	
	public static <T> T[] copyArray2Array(Object[] source, Class<T> target) throws RuntimeException {
		return copyArray2Array(source, target, null, false);
	}
	public static <T> T[] copyArray2Array(Object[] source, Class<T> target,PropertyConverter propertyConverter) throws RuntimeException {
		return copyArray2Array(source, target, propertyConverter, false);
	}
	public static <T> T[] copyArray2Array(Object[] source, Class<T> target,PropertyConverter propertyConverter, boolean replaceConverter) throws RuntimeException {
		if (source == null || source.length == 0) {
			return null;
		}
		@SuppressWarnings("unchecked")
		T[] result = (T[]) Array.newInstance(target, source.length);
		for (int i = 0; i < source.length; i++) {
			result[i] = copyOne2One(source[i], target, propertyConverter, replaceConverter);
		}
		return result;
	}

	/**
	 * 深度拷贝，包括父类直到Object。反射实现<br/>
	 * 不缓存反射字段<br/>
	 * 适合没有getter/setter方法的Bean之间拷贝
	 * @param source
	 * @param target
	 * @throws RuntimeException
	 */
	public static void deeplyCopyAttribute(Object source, Object target) throws RuntimeException{
		if (source == null || target == null) {
			return;
		}
		Class<?> sourceClazz = source.getClass();
		if (sourceClazz != Object.class) {
			Map<String, Field> fields = new HashMap<>();
			while(sourceClazz != null && sourceClazz != Object.class) {
				for(Field f:sourceClazz.getDeclaredFields()){
					fields.put(f.getName(),f);
				}
				sourceClazz = sourceClazz.getSuperclass();
			}
			try{
				Class<?> targetClazz=target.getClass();
				while (targetClazz != Object.class) {
					for(Field f:targetClazz.getDeclaredFields()){
						if(fields.keySet().contains(f.getName())) {
							Field sf = fields.get(f.getName());
							if (sf.getType() == f.getType() && sf.getGenericType() == f.getGenericType()) {
								f.setAccessible(true);
								sf.setAccessible(true);
								f.set(target, sf.get(source));
							}
						}
					}
					targetClazz = targetClazz.getSuperclass();
				}
			}catch(Exception e){
				throw new RuntimeException(e);
			}
		}
	}

	/**
	 * 深度拷贝，包括父类直到Object。反射实现<br/>
	 * 缓存反射字段<br/>
	 * 适合没有getter/setter方法的Bean之间拷贝
	 * @param source
	 * @param target
	 * @throws RuntimeException
	 */
	public static void copyAllAttribute(Object source, Object target) throws RuntimeException {
		if (source == null || target == null) {
			return;
		}
		String key = generateKey(source.getClass(), target.getClass(), false);
		Field[][] fields = FIELD_CACHE.get(key);
		if (fields == null) {
			fields = fieldGetter.apply(source.getClass(), target.getClass());
			FIELD_CACHE.put(key, fields);
		}
		if (fields == null) {
			return;
		}
		try{
			for (Field[] field : fields) {
				field[1].set(target, field[0].get(source));
			}
		} catch (Exception e) {
			throw new RuntimeException("copy properties failed!", e);
		}
	}
	private static BiFunction<Class, Class, Field[][]> fieldGetter = (sourceClazz, targetClazz) -> {
		if (sourceClazz == Object.class || targetClazz == Object.class) {
			return null;
		}
		List<Field[]> list=new ArrayList<>();
		Map<String, Field> fields = new HashMap<>();
		while(sourceClazz != null && sourceClazz != Object.class) {
			for(Field f:sourceClazz.getDeclaredFields()){
				fields.put(f.getName(),f);
			}
			sourceClazz = sourceClazz.getSuperclass();
		}
		try{
			while (targetClazz != Object.class) {
				for (Field tf : targetClazz.getDeclaredFields()) {
					if (fields.keySet().contains(tf.getName())) {
						Field sf = fields.get(tf.getName());
						if (sf.getType() == tf.getType() && sf.getGenericType() == tf.getGenericType()) {
							tf.setAccessible(true);
							sf.setAccessible(true);
							list.add(new Field[]{sf, tf});
						}
					}
				}
				targetClazz = targetClazz.getSuperclass();
			}
		}catch(Exception e){
			throw new RuntimeException(e);
		}
		return list.toArray(new Field[list.size()][2]);
	};
	/**
	 * copy Properties of superclass of parent(include parent) to child<br/>
	 * 适合没有getter/setter方法的Bean之间拷贝
	 * @param parent
	 * @param child
	 * @throws RuntimeException
	 */
	public static void copyParentAttribute(Object parent, Object child) throws RuntimeException{
		if (parent == null || child == null) {
			return;
		}
		try{
			Class<?> parentClass = parent.getClass();
			Class<?> superClazz=child.getClass().getSuperclass();
			while (superClazz != null && superClazz != Object.class) {
				if (parentClass.isAssignableFrom(superClazz)) {
					for(Field f:superClazz.getDeclaredFields()){
						f.setAccessible(true);
						f.set(child, f.get(parent));
					}
				}
				superClazz = superClazz.getSuperclass();
			}
		}catch(Exception e){
			throw new RuntimeException(e);
		}
	}
	
	public interface PropertyConverter{
		public Object convert(Object sourcePropertyValue, @SuppressWarnings("rawtypes") Class targetPropertyClass, Object setterNameOrContext);
	}
}
